cmake_minimum_required(VERSION 3.16 FATAL_ERROR)

if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/CTestConfig.cmake")
    include("${CMAKE_CURRENT_LIST_DIR}/CTestConfig.cmake")
endif()

include(ProcessorCount)
ProcessorCount(CTEST_PROCESSOR_COUNT)

cmake_policy(SET CMP0009 NEW)
cmake_policy(SET CMP0011 NEW)

# ---------------------------------------------------------------------------- #
# -- Commands
# ---------------------------------------------------------------------------- #
find_program(CTEST_CMAKE_COMMAND    NAMES cmake)
find_program(CTEST_UNAME_COMMAND    NAMES uname)

find_program(CTEST_BZR_COMMAND      NAMES bzr)
find_program(CTEST_CVS_COMMAND      NAMES cvs)
find_program(CTEST_GIT_COMMAND      NAMES git)
find_program(CTEST_HG_COMMAND       NAMES hg)
find_program(CTEST_P4_COMMAND       NAMES p4)
find_program(CTEST_SVN_COMMAND      NAMES svn)

find_program(VALGRIND_COMMAND       NAMES valgrind)
find_program(GCOV_COMMAND           NAMES gcov)
find_program(LCOV_COMMAND           NAMES llvm-cov)
find_program(MEMORYCHECK_COMMAND    NAMES valgrind )

set(MEMORYCHECK_TYPE Valgrind)
# set(MEMORYCHECK_TYPE Purify)
# set(MEMORYCHECK_TYPE BoundsChecker)
# set(MEMORYCHECK_TYPE ThreadSanitizer)
# set(MEMORYCHECK_TYPE AddressSanitizer)
# set(MEMORYCHECK_TYPE LeakSanitizer)
# set(MEMORYCHECK_TYPE MemorySanitizer)
# set(MEMORYCHECK_TYPE UndefinedBehaviorSanitizer)
set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full")

# ---------------------------------------------------------------------------- #
# -- Settings
# ---------------------------------------------------------------------------- #
## -- Process timeout in seconds
set(CTEST_TIMEOUT           "7200")
## -- Set output to English
set(ENV{LC_MESSAGES}        "en_EN" )


# ---------------------------------------------------------------------------- #
# -- Copy ctest configuration file
# ---------------------------------------------------------------------------- #
macro(COPY_CTEST_CONFIG_FILES)

    foreach(_FILE CTestConfig.cmake CTestCustom.cmake)

        # if current directory is not binary or source directory
        if(NOT "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "${CTEST_BINARY_DIRECTORY}" AND
           NOT "${CTEST_SOURCE_DIRECTORY}" STREQUAL "${CTEST_BINARY_DIRECTORY}")

            # if file exists in current directory
            if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${_FILE})
                configure_file(${CMAKE_CURRENT_LIST_DIR}/${_FILE}
                    ${CTEST_BINARY_DIRECTORY}/${_FILE} COPYONLY)
            endif()

        # if source and binary differ
        elseif(NOT "${CTEST_SOURCE_DIRECTORY}" STREQUAL "${CTEST_BINARY_DIRECTORY}")

            # if file exists in source directory but not in binary directory
            if(EXISTS ${CTEST_SOURCE_DIRECTORY}/${_FILE} AND
               NOT EXISTS ${CTEST_BINARY_DIRECTORY}/${_FILE})
                configure_file(${CTEST_SOURCE_DIRECTORY}/${_FILE}
                    ${CTEST_BINARY_DIRECTORY}/${_FILE} COPYONLY)
            endif()

        endif()
    endforeach()

endmacro()

ctest_read_custom_files("${CMAKE_CURRENT_LIST_DIR}")

message(STATUS "CTEST_MODEL: ${CTEST_MODEL}")

#-------------------------------------------------------------------------#
# Start
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running START_CTEST stage...")
message(STATUS "")

ctest_start(${CTEST_MODEL} TRACK ${CTEST_MODEL} ${APPEND_CTEST}
    ${CTEST_SOURCE_DIRECTORY} ${CTEST_BINARY_DIRECTORY})


#-------------------------------------------------------------------------#
# Config
#
copy_ctest_config_files()
ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}")


#-------------------------------------------------------------------------#
# Update
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_UPDATE stage...")
message(STATUS "")

ctest_update(SOURCE "${CTEST_SOURCE_DIRECTORY}"
    RETURN_VALUE up_ret)


#-------------------------------------------------------------------------#
# Configure
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_CONFIGURE stage...")
message(STATUS "")

ctest_configure(BUILD "${CTEST_BINARY_DIRECTORY}"
    SOURCE ${CTEST_SOURCE_DIRECTORY}
    ${APPEND_CTEST}
    OPTIONS "${CTEST_CONFIGURE_OPTIONS}"
    RETURN_VALUE config_ret)


#-------------------------------------------------------------------------#
# Echo configure log bc Damien wants to delay merging this PR for eternity
#
file(GLOB _configure_log "${CTEST_BINARY_DIRECTORY}/Testing/Temporary/LastConfigure*.log")
# should only have one but loop just for safety
foreach(_LOG ${_configure_log})
    file(READ ${_LOG} _LOG_MESSAGE)
    message(STATUS "Configure Log: ${_LOG}")
    message(STATUS "\n${_LOG_MESSAGE}\n")
endforeach()


#-------------------------------------------------------------------------#
# Build
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_BUILD stage...")
message(STATUS "")

ctest_build(BUILD "${CTEST_BINARY_DIRECTORY}"
    ${APPEND_CTEST}
    RETURN_VALUE build_ret)


#-------------------------------------------------------------------------#
# Echo build log bc Damien wants to delay merging this PR for eternity
#
file(GLOB _build_log "${CTEST_BINARY_DIRECTORY}/Testing/Temporary/LastBuild*.log")
# should only have one but loop just for safety
foreach(_LOG ${_build_log})
    file(READ ${_LOG} _LOG_MESSAGE)
    message(STATUS "Build Log: ${_LOG}")
    message(STATUS "\n${_LOG_MESSAGE}\n")
endforeach()


#-------------------------------------------------------------------------#
# Test
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_TEST stage...")
message(STATUS "")

ctest_test(RETURN_VALUE test_ret
    ${APPEND_CTEST}
    ${START_CTEST}
    ${END_CTEST}
    ${STRIDE_CTEST}
    ${INCLUDE_CTEST}
    ${EXCLUDE_CTEST}
    ${INCLUDE_LABEL_CTEST}
    ${EXCLUDE_LABEL_CTEST}
    ${PARALLEL_LEVEL_CTEST}
    ${STOP_TIME_CTEST}
    SCHEDULE_RANDOM OFF)


#-------------------------------------------------------------------------#
# Coverage
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_COVERAGE stage...")
message(STATUS "")

execute_process(COMMAND ${CTEST_COVERAGE_COMMAND} ${CTEST_COVERAGE_EXTRA_FLAGS}
    WORKING_DIRECTORY ${CTEST_BINARY_DIRECTORY}
    ERROR_QUIET)

ctest_coverage(${APPEND_CTEST}
    ${CTEST_COVERAGE_LABELS}
    RETURN_VALUE cov_ret)


#-------------------------------------------------------------------------#
# MemCheck
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_MEMCHECK stage...")
message(STATUS "")

ctest_memcheck(RETURN_VALUE mem_ret
    ${APPEND_CTEST}
    ${START_CTEST}
    ${END_CTEST}
    ${STRIDE_CTEST}
    ${INCLUDE_CTEST}
    ${EXCLUDE_CTEST}
    ${INCLUDE_LABEL_CTEST}
    ${EXCLUDE_LABEL_CTEST}
    ${PARALLEL_LEVEL_CTEST})


#-------------------------------------------------------------------------#
# Submit
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Running CTEST_SUBMIT stage...")
message(STATUS "")

file(GLOB_RECURSE NOTE_FILES "${CTEST_BINARY_DIRECTORY}/*CTestNotes.cmake")
foreach(_FILE ${NOTE_FILES})
    message(STATUS "Including CTest notes files: \"${_FILE}\"...")
    include("${_FILE}")
endforeach()

# capture submit error so it doesn't fail because of a submission error
ctest_submit(RETURN_VALUE submit_ret
    RETRY_COUNT 2
    RETRY_DELAY 10
    CAPTURE_CMAKE_ERROR submit_err)

#-------------------------------------------------------------------------#
# Submit
#
message(STATUS "")
message(STATUS "[${CTEST_BUILD_NAME}] Finished ${CTEST_MODEL} Stages (${STAGES})")
message(STATUS "")


#-------------------------------------------------------------------------#
# Non-zero exit codes for important errors
#
if(NOT config_ret EQUAL 0)
    message(FATAL_ERROR "Error during configuration! Exit code: ${config_ret}")
endif()

if(NOT build_ret EQUAL 0)
    message(FATAL_ERROR "Error during build! Exit code: ${build_ret}")
endif()

if(NOT test_ret EQUAL 0)
    message(FATAL_ERROR "Error during testing! Exit code: ${test_ret}")
endif()
